package com.ibm.risk.irmp.rule.drools;

import com.ibm.risk.irmp.common.vo.PagedParam;
import com.ibm.risk.irmp.common.vo.PagedResult;
import com.ibm.risk.irmp.rule.data.DataExporter;
import com.ibm.risk.irmp.rule.data.DataImporter;
import com.ibm.risk.irmp.rule.vo.RuleContent;
import com.ibm.risk.irmp.rule.vo.RuleDomain;
import com.ibm.risk.irmp.rule.vo.RuleResult;
import com.ibm.risk.irmp.rule.vo.RuleSumResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.drools.compiler.kie.builder.impl.InternalKieModule;
import org.drools.compiler.kie.builder.impl.KieContainerImpl;
import org.kie.api.KieBase;
import org.kie.api.KieServices;
import org.kie.api.builder.KieBuilder;
import org.kie.api.builder.KieFileSystem;
import org.kie.api.builder.Message;
import org.kie.api.builder.Results;
import org.kie.api.builder.model.KieBaseModel;
import org.kie.api.builder.model.KieModuleModel;
import org.kie.api.definition.rule.Rule;
import org.kie.api.definition.type.FactType;
import org.kie.api.runtime.ClassObjectFilter;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.*;

import static com.ibm.risk.irmp.rule.data.DataExporter.FACT_TYPE;

/**
 * drools 管理
 */
@Component
@Slf4j
public class DroolsManager {

    // 此类本身就是单例的
    private final KieServices kieServices = KieServices.Factory.get();
    // kie文件系统，需要缓存，如果每次添加规则都是重新new一个的话，则可能出现问题。即之前加到文件系统中的规则没有了
    private final KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
    // 可以理解为构建 kmodule.xml
    private final KieModuleModel kieModuleModel = kieServices.newKieModuleModel();
    @Autowired
    List<DataImporter> dataImporters;
    @Autowired
    List<DataExporter> dataExporters;
    // 需要全局唯一一个，如果每次加个规则都新创建一个，那么旧需要销毁之前创建的kieContainer，如果此时有正在使用的KieSession，则可能有问题
    private KieContainer kieContainer;

    @Value("${app.irmp.common.rule.batch-size}")
    int batchSize;

    private static Class<?> getFactClass(String domainCode, String clsName, KieBase kieBase) throws Exception {
        final Class<?> lcs;
        if (StringUtils.isNotEmpty(clsName)) {
            if (clsName.contains(".")) {
                lcs = Class.forName(clsName);
            } else {
                FactType type = kieBase.getFactType(domainCode, clsName);
                if (type != null) {
                    lcs = type.getFactClass();
                } else {
                    throw new RuntimeException("找不到对应的outClass:" + clsName);
                }
            }
        } else {
            lcs = null;
        }
        return lcs;
    }

    private static FactType getFactType(String domainCode, String outClass, KieBase kieBase) throws ClassNotFoundException {
        if (StringUtils.isNotEmpty(outClass) && !outClass.contains(".")) {
            FactType type = kieBase.getFactType(domainCode, outClass);
            return type;
        }
        return null;
    }

    /**
     * 判断该kbase是否存在
     */
    public boolean existsKieBase(String kieBaseName) {
        if (null == kieContainer) {
            return false;
        }
        Collection<String> kieBaseNames = kieContainer.getKieBaseNames();
        if (kieBaseNames.contains(kieBaseName)) {
            return true;
        }
        log.info("需要创建KieBase:{}", kieBaseName);
        return false;
    }

    public void deleteDroolsRule(String kieBaseName, String packageName, String ruleName) {
        if (existsKieBase(kieBaseName)) {
            KieBase kieBase = kieContainer.getKieBase(kieBaseName);
            Rule rule = kieBase.getRule(packageName, ruleName);
            if (rule != null) {
                kieBase.removeRule(packageName, ruleName);
                log.info("删除kieBase:[{}]包:[{}]下的规则:[{}]", kieBaseName, packageName, ruleName);
            } else {
                log.info("kieBase:[{}]包:[{}]下没有规则:[{}]", kieBaseName, packageName, ruleName);
            }

        }
    }

    /**
     * 添加或更新 drools 规则
     */
    public void addOrUpdateRule(RuleContent ruleContent) {
        // 获取kbase的名称
        String kieBaseName = ruleContent.getDomainCode();
        String packName = ruleContent.getRulePackage();
        // 判断该kbase是否存在
        boolean existsKieBase = existsKieBase(kieBaseName);
        // 该对象对应kmodule.xml中的kbase标签
        KieBaseModel kieBaseModel = null;
        if (!existsKieBase) {
            // 创建一个kbase
            kieBaseModel = kieModuleModel.newKieBaseModel(kieBaseName);
            // 不是默认的kieBase
            kieBaseModel.setDefault(false);
            // 设置该KieBase需要加载的包路径
            kieBaseModel.addPackage(packName);
            // 设置kieSession
            kieBaseModel.newKieSessionModel(kieBaseName)
                    // 不是默认session
                    .setDefault(false);
        } else {
            // 获取到已经存在的kbase对象
            kieBaseModel = kieModuleModel.getKieBaseModels().get(kieBaseName);
            // 获取到packages
            List<String> packages = kieBaseModel.getPackages();
            if (!packages.contains(packName)) {
                kieBaseModel.addPackage(packName);
                log.info("kieBase:{}添加一个新的包:{}", kieBaseName, packName);
            } else {
                kieBaseModel = null;
            }
        }

        String file = "src/main/resources/" + packName + "/" + ruleContent.getCode() + ".drl";
        log.info("加载虚拟规则文件:{}", file);
        kieFileSystem.write(file, ruleContent.getRuleContent());

        if (kieBaseModel != null) {
            String kmoduleXml = kieModuleModel.toXML();
            log.info("加载kmodule.xml:[\n{}]", kmoduleXml);
            kieFileSystem.writeKModuleXML(kmoduleXml);
        }

        KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem);
        // 通过KieBuilder构建KieModule下所有的KieBase
        kieBuilder.buildAll();
        // 获取构建过程中的结果
        Results results = kieBuilder.getResults();
        // 获取错误信息
        List<Message> messages = results.getMessages(Message.Level.ERROR);
        if (null != messages && !messages.isEmpty()) {
            for (Message message : messages) {
                log.error(message.getText());
            }
            throw new RuntimeException("加载规则出现异常");
        }
        // KieContainer只有第一次时才需要创建，之后就是使用这个
        if (null == kieContainer) {
            kieContainer = kieServices.newKieContainer(kieServices.getRepository().getDefaultReleaseId());
        } else {
            // 实现动态更新
            ((KieContainerImpl) kieContainer).updateToKieModule((InternalKieModule) kieBuilder.getKieModule());
        }
    }

    /**
     * 触发规则，此处简单模拟，会向规则中插入一个Integer类型的值
     */
    public RuleResult<?> execRule(RuleDomain ruleDomain, List<?> dataList, Map<String, Object> params) throws Exception {
        if (dataList == null || dataList.isEmpty()) {
            RuleResult<Object> rst = new RuleResult<>();
            rst.setSuccess(true);
            rst.setMessage("没有数据");
            return rst;
        }
        // 创建kieSession
        log.info("创建kieSession:{}", ruleDomain);
        KieSession kieSession = kieContainer.newKieSession(ruleDomain.getCode());
        RuleResult<?> output = new RuleResult<>();
        kieSession.setGlobal("log", log);
        kieSession.setGlobal("output", output);
//        params.forEach(kieSession::setGlobal);
        KieBase kieBase = kieContainer.getKieBase(ruleDomain.getCode());
        if (dataList.iterator().next() instanceof Map) {
            final FactType factType;
            if (StringUtils.isNotEmpty(ruleDomain.getFactClass())) {
                factType = kieBase.getFactType(RuleContent.RULE_PACKAGE_PREFIX + ruleDomain.getCode(), ruleDomain.getFactClass());
                if (factType == null) {
                    log.error("找不到对应的FactType, domain:{}", ruleDomain);
                    throw new RuntimeException("找不到对应的FactType, domain:" + ruleDomain);
                }
            } else {
                factType = null;
            }

            if (factType != null) {
                ArrayList<Object> newDataList = new ArrayList<>(dataList.size());
                for (Object o : dataList) {
                    Object fact = factType.newInstance();
                    Map<String, Object> map = (Map<String, Object>) o;
                    factType.setFromMap(fact, map);
                    newDataList.add(fact);
                }
                dataList = newDataList;
            }
        }
        dataList.forEach(kieSession::insert);

        // 执行规则
        kieSession.fireAllRules();

        // 获取输出
        Class<?> outc = getFactClass(ruleDomain.getCode(), ruleDomain.getOutClass(), kieBase);
        Collection result;
        if (outc != null) {
            result = kieSession.getObjects(new ClassObjectFilter(outc));

        } else {
            result = dataList;
        }
        output.setDataList(result);

        kieSession.dispose();
        return output;
    }

    public RuleSumResult fireRule(RuleDomain ruleDomain, Map<String, Object> params) {
        String dataAccessType = ruleDomain.getDataAccessType();
        DataImporter dataImporter = findDataImporter(dataAccessType);
        if (dataImporter == null) {
            throw new RuntimeException("找不到对应的DataImporter, domain:" + ruleDomain);
        }
//        params.forEach(kieSession::setGlobal);

        PagedParam pagedParam = new PagedParam(0, batchSize);
        int cnt = 0;
        Integer total = 0;
        Map<String, Object> dataImpParams = new HashMap<>();
        Map<String, Object> dataExpParams = new HashMap<>();
        if (params != null) {
            dataImpParams.putAll(params);
            dataExpParams.putAll(params);
        }
        if (ruleDomain.getDataImpParams() != null) {
            dataImpParams.putAll(ruleDomain.getDataImpParams());
        }
        KieBase kieBase = kieContainer.getKieBase(ruleDomain.getCode());
//        Boolean isMapData = null;
        RuleResult<?> output = new RuleResult<>();
        try {
            Class<?> inClass = getFactClass(RuleContent.RULE_PACKAGE_PREFIX + ruleDomain.getCode(), ruleDomain.getFactClass(), kieBase);
            while (true) {
                PagedResult<?> pagedResult = dataImporter.readData(dataImpParams, pagedParam, inClass == null ? null : inClass);
//                ArrayList<Object> dataList = new ArrayList<>(pagedResult.getList().size());
                cnt += pagedResult.getList().size();
                if (total == 0) {
                    total = pagedResult.getTotal();
                }
                if (pagedResult.getList() != null && !pagedResult.getList().isEmpty()) {
                    KieSession kieSession = kieContainer.newKieSession(ruleDomain.getCode());
//        StatelessKieSession statelessKieSession = kieContainer.newStatelessKieSession(ruleDomain.getCode());
                    kieSession.setGlobal("log", log);
                    kieSession.setGlobal("output", output);
                    pagedResult.getList().forEach(kieSession::insert);

                    // 执行规则
                    int i = kieSession.fireAllRules();
                    log.info("{} rules executed.", i);

                    // 获取输出数据
                    final Class<?> outc = getFactClass(RuleContent.RULE_PACKAGE_PREFIX + ruleDomain.getCode(), ruleDomain.getOutClass(), kieBase);
                    Collection<?> dataList = null;
                    if (outc != null) {
                        dataList = kieSession.getObjects(o -> outc.isAssignableFrom(o.getClass()));
                        dataExpParams.put(FACT_TYPE, getFactType(RuleContent.RULE_PACKAGE_PREFIX + ruleDomain.getCode(), ruleDomain.getOutClass(), kieBase));
                    } else {
                        dataList = kieSession.getObjects();
                        dataExpParams.put(FACT_TYPE, getFactType(RuleContent.RULE_PACKAGE_PREFIX + ruleDomain.getCode(), ruleDomain.getFactClass(), kieBase));
                    }
                    log.info("rules engine message: {}", output.getMessage());
                    DataExporter dataExporter = findDataExporter(dataAccessType);
                    if (ruleDomain.getDataExpParams() != null) {
                        dataExpParams.putAll(ruleDomain.getDataExpParams());
                    }
                    int c = dataExporter.exportData(dataExpParams, dataList);
                    log.info("data exported count: {}", c);
                    kieSession.dispose();
                } else {
                    break;
                }
                if (cnt >= total) {
                    break;
                }
                pagedParam.nextPage();
            }
        } catch (Exception e) {
            log.error("规则执行失败", e);
            output.setSuccess(false);
            output.setMessage(e.getMessage());
        }
        log.info("Total calculated: {}", cnt);
        output.setDataList(null);
        return output;
    }

    private DataImporter findDataImporter(String dataAccessType) {
        for (DataImporter dataImporter : dataImporters) {
            if (dataImporter.getDataAccessType().equals(dataAccessType)) {
                return dataImporter;
            }
        }
        return null;
    }

    private DataExporter findDataExporter(String dataAccessType) {
        for (DataExporter dataExporter : dataExporters) {
            if (dataExporter.getDataAccessType().equals(dataAccessType)) {
                return dataExporter;
            }
        }
        return null;
    }
}
